package com.sandwormz.webcompiler;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.sandwormz.webcompiler.Page.TextElement;

/**
 * Some utility functions for locating the real paths of links contained within
 * an HTML document.
 * 
 * Created: Dec 30, 2012 11:36:58 AM
 * 
 * @author mbryant
 * 
 */
public class PathTranslator {

    public static class PathTranslatorBuilder {
        private String documentParentDirectory = null;

        @SuppressWarnings("synthetic-access")
        public PathTranslator build() {
            Preconditions.checkState(this.documentParentDirectory != null,
                    "documentParentDirectory is required.");
            return new PathTranslator(this.documentParentDirectory);
        }

        public PathTranslatorBuilder parentDirectory(
                final String documentParentDirectory) {
            this.documentParentDirectory = documentParentDirectory;
            return this;
        }
    }

    protected static Pattern allPathsPattern = Pattern.compile(
            "(?:src|href)=\"([^\"]*)\"", Pattern.MULTILINE);

    protected static Pattern scriptUrlPattern = Pattern.compile(
            "\\<script[^>]*src=\"([^\"]*)\".*", Pattern.MULTILINE);

    public final static String[] ABSOLUTE_PATH_PREFIXES = { "/", "http://",
            "https://" };

    public static PathTranslatorBuilder Builder() {
        return new PathTranslatorBuilder();
    }

    /**
     * @param testPath
     * @return
     */
    protected static boolean isAbsolute(final String path) {
        String lowerPath = path.toLowerCase();

        for (String prefix : ABSOLUTE_PATH_PREFIXES) {
            if (lowerPath.startsWith(prefix)) {
                return true;
            }
        }

        return false;
    }

    protected final String rootPath;

    private Map<String, String> pathTranslations =
            new HashMap<String, String>();

    private static final Logger _logger = LoggerFactory
            .getLogger(PathTranslator.class);

    /**
     * Create a new normalizer by specifying its parent directory.
     * 
     * @param documentParentDirectory
     */
    private PathTranslator(final String documentParentDirectory) {
        _logger.trace("Creating new translator with parent: "
                + documentParentDirectory);

        Preconditions.checkArgument(documentParentDirectory.endsWith("/"),
                "path must be a directory.");
        this.rootPath = documentParentDirectory;
    }

    /**
     * @param option
     * @param option2
     */
    public void addPathTranslation(final String regex, final String replacement) {
        this.pathTranslations.put(regex, replacement);
    }

    public List<String> findAllJavscriptPathsInFragment(
            final List<TextElement> containedFragments) {
        LinkedList<String> sourceFiles = new LinkedList<String>();
        for (TextElement textElement : containedFragments) {
            List<String> sourcesPaths =
                    this.findAllJavscriptPathsInFragment(textElement.content);

            sourceFiles.addAll(sourcesPaths);
        }
        return sourceFiles;
    }

    /**
     * @param containedHTML
     * @return
     */
    public List<String> findAllJavscriptPathsInFragment(
            final String containedHTML) {
        return extractMatchingPaths(scriptUrlPattern, containedHTML);
    }

    /**
     * @param containedHTML
     * @return
     */
    public LinkedList<String> findAllPathsInFragment(final String containedHTML) {
        return extractMatchingPaths(allPathsPattern, containedHTML);
    }

    /**
     * Translates the specified path relative for this translator's root
     * directory and any path translations present.
     * 
     * @param testPath
     * @return
     */
    public String translatePath(final String path) {

        String output = path;
        for (String search : this.pathTranslations.keySet()) {
            String replace = this.pathTranslations.get(search);
            output = output.replaceAll(search, replace);
        }

        if (PathTranslator.isAbsolute(output)) {

        } else {
            output = this.rootPath + output;
        }

        _logger.trace("Translating from " + path + " to " + output);

        return output;
    }

    /**
     * @param annotation
     * @param annotationPattern
     * @param containedHTML
     * @return
     */
    protected LinkedList<String> extractMatchingPaths(
            final Pattern annotationPattern, final String containedHTML) {

        LinkedList<String> matches = new LinkedList<String>();

        Matcher matcher = annotationPattern.matcher(containedHTML);

        while (matcher.find()) {

            String path = matcher.group(1);

            String realPath = translatePath(path);

            matches.add(realPath);

        }
        return matches;
    }

}
